package huriezy.quickcontacts.models.managers;

import huriezy.quickcontacts.models.LogDebug;
import huriezy.quickcontacts.models.interfaces.IAddressDetectionListener;
import huriezy.quickcontacts.models.interfaces.ILocationManager;

import java.io.IOException;
import java.util.List;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;

public class ContactGoogleServicesLocationManager implements ILocationManager {

	public static final int				CONNECTION_FAILURE_RESOLUTION_REQUEST	= 9000;
	private static final int			GEOCODER_MAX_RESULTS					= 5;
	private static final long			UPDATE_INTERVAL							= 5000;
	private static final long			FASTEST_UPDATE_INTERVAL					= 1000;

	private ConnectionLocationListener	connectionListener						= null;
	private LocationRequest				locationRequest							= null;
	private LocationClient				locationClient							= null;
	private SharedPreferences			sharedPreferences						= null;
	private Editor						preferencesEditor						= null;
	private boolean						updateRequested							= true;
	private Geocoder					geocoder								= null;

	private final Context				parentContext;
	private IAddressDetectionListener	addressListener;

	public ContactGoogleServicesLocationManager(Context context) {
		this.parentContext = context;
	}

	/**
	 * Init all needed location detection objects.
	 */
	public void initLocationConnections() {
		testGoogleServicesAvailability();
		sharedPreferences = parentContext.getSharedPreferences("SharedPreferences", Context.MODE_PRIVATE);
		preferencesEditor = sharedPreferences.edit();
		geocoder = new Geocoder(parentContext);

		connectionListener = new ConnectionLocationListener();

		locationClient = new LocationClient(parentContext, connectionListener, connectionListener);

		locationRequest = new LocationRequest();
		locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
		locationRequest.setInterval(UPDATE_INTERVAL);
		locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
	}

	/**
	 * Verify if the google services are available, else we show the API error
	 * dialog to handle the error.
	 */
	private void testGoogleServicesAvailability() {
		int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(parentContext);
		if (ConnectionResult.SUCCESS == resultCode) {
			LogDebug.info("Google Play services is available.", getClass());
		}
		else {
			showErrorDialog(resultCode);
		}
	}

	@Override
	public void startLocationUpdates() {
		if (locationRequest != null && connectionListener != null) {
			locationClient.requestLocationUpdates(locationRequest, connectionListener);
		}
		else {
			LogDebug.warn(
					"Cannot start location updates. Call the method initLocationConnections before any call to this method.",
					parentContext);
		}
	}

	public void stopLocationUpdates() {
		if (locationClient.isConnected()) {
			locationClient.removeLocationUpdates(connectionListener);
		}
		locationClient.disconnect();
	}

	public void saveUpdateStatus() {
		preferencesEditor.putBoolean("KEY_UPDATES_ON", updateRequested);
		preferencesEditor.commit();
	}

	public void loadUpdateStatus() {
		if (sharedPreferences.contains("KEY_UPDATES_ON")) {
			updateRequested = sharedPreferences.getBoolean("KEY_UPDATES_ON", false);
			LogDebug.info("Is updates on in shared preferences : " + updateRequested, getClass());
		}
		else {
			preferencesEditor.putBoolean("KEY_UPDATES_ON", updateRequested);
			preferencesEditor.commit();
		}
	}

	/**
	 * Translate address into a readable String.
	 * 
	 * @param address
	 *            the concerned Address
	 * @return the readable {@link String}
	 */
	public String addressToString(Address address) {
		StringBuilder result = new StringBuilder();
		boolean first = true;
		for (int index = 0; index == address.getMaxAddressLineIndex(); ++index) {
			if (!first) {
				result.append(",");
			}
			result.append(address.getAddressLine(index));
			first = false;
		}
		return result.toString();
	}

	/**
	 * Ask the {@link Geocoder} object to translate a {@link Location} into a
	 * list of {@link Address}.
	 * 
	 * @param currentLocation
	 */
	public Address getAddressFromLocation(Location currentLocation) {
		Address result = null;
		LogDebug.info("Geocoder is " + geocoder + " and location is " + currentLocation, getClass());
		if (geocoder != null && currentLocation != null) {
			try {
				List<Address> listAddress = geocoder.getFromLocation(currentLocation.getLatitude(),
						currentLocation.getLongitude(), GEOCODER_MAX_RESULTS);
				if (listAddress != null && !listAddress.isEmpty()) {
					result = listAddress.get(0);
				}
				LogDebug.info("geocoder result is " + result, getClass());
			}
			catch (IOException e) {
				LogDebug.error("Cannot retrieve address list from location " + currentLocation, parentContext, e);
			}
		}
		return result;
	}

	/**
	 * @return the last registered {@link Location}.
	 */
	public Location getLastKnownLocation() {
		Location result = null;
		result = locationClient.getLastLocation();
		return result;
	}

	@Override
	public boolean isProviderAvailable() {

		// TODO Auto-generated method stub

		return false;
	}

	@Override
	public void setAddressDetectionListener(IAddressDetectionListener listener) {
		if (listener != null) {
			addressListener = listener;
		}
	}

	/**
	 * Show standard error dialog from Google Services, using the donate error
	 * code.
	 * 
	 * @param resultCode
	 */
	private void showErrorDialog(int resultCode) {
		Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(resultCode, (Activity) parentContext,
				CONNECTION_FAILURE_RESOLUTION_REQUEST);
		errorDialog.show();
	}

	/**
	 * A listener class that handles {@link LocationClient} updates.
	 */
	public class ConnectionLocationListener implements ConnectionCallbacks, OnConnectionFailedListener,
			LocationListener {

		@Override
		public void onLocationChanged(Location location) {
			String msg = "Updated Location: " + Double.toString(location.getLatitude()) + ","
					+ Double.toString(location.getLongitude());
			// Toast.makeText(parentContext, msg, Toast.LENGTH_SHORT).show();
			LogDebug.info(msg, getClass());
			Address address = getAddressFromLocation(location);
			if (address != null && addressListener != null) {
				addressListener.onAddressDetected(address, location.getAccuracy());
			}
		}

		@Override
		public void onConnectionFailed(ConnectionResult connectionResult) {
			if (connectionResult.hasResolution()) {
				try {
					connectionResult.startResolutionForResult((Activity) parentContext,
							CONNECTION_FAILURE_RESOLUTION_REQUEST);
				}
				catch (IntentSender.SendIntentException e) {
					LogDebug.error("Cannot start resolution for result", parentContext, e);
				}
			}
			else {
				showErrorDialog(connectionResult.getErrorCode());
			}
		}

		@Override
		public void onConnected(Bundle dataBundle) {
			// Toast.makeText(parentContext, "Connection started",
			// Toast.LENGTH_SHORT).show();
			LogDebug.info("Request location updates", getClass());

		}

		@Override
		public void onDisconnected() {
			Toast.makeText(parentContext, "Connection lost. Please re-connect.", Toast.LENGTH_SHORT).show();
		}
	}

}
